﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
using AIPASoft.CommonLibrary.WPF.MVVM.Behavior;

namespace AIPASoft.CommonLibrary.WPF.MVVM.ViewModel
{
    /// <summary>
    /// A base classe for ViewModel classes which supports validation using IDataErrorInfo interface. Properties must defines
    /// validation rules by using validation attributes defined in System.ComponentModel.DataAnnotations.
    /// </summary>
    public class ValidationViewModel : ViewModelBase, IDataErrorInfo , IValidationExceptionHandler
    {
        private readonly Dictionary<string, Func<ValidationViewModel, object>> _propertyGetters;
        private readonly Dictionary<string, ValidationAttribute[]> _validators;

        /// <summary>
        /// Gets the error message for the property with the given name.
        /// </summary>
        /// <param name="propertyName">Name of the property</param>
        public string this[string propertyName]
        {
            get
            {
                if (_propertyGetters.ContainsKey(propertyName))
                {
                    //Get Specified property value
                    var propertyValue = _propertyGetters[propertyName](this);

                    //Validate and get error message
                    var errorMessages = _validators[propertyName]
                        .Where(v => !v.IsValid(propertyValue))
                        .Select(v => v.ErrorMessage).ToArray();

                    //return error message
                    return string.Join(Environment.NewLine, errorMessages);
                }

                return string.Empty;
            }
        }

        /// <summary>
        /// Gets an error message indicating what is wrong with this object.
        /// </summary>
        public string Error
        {
            get
            {
                var errors = from validator in _validators
                             from attribute in validator.Value
                             where !attribute.IsValid(_propertyGetters[validator.Key](this))
                             select attribute.ErrorMessage;

                return string.Join(Environment.NewLine, errors.ToArray());
            }
        }

        /// <summary>
        /// Gets the number of properties which have a validation attribute and are currently valid
        /// </summary>
        public int ValidPropertiesCount
        {
            get
            {
                var query = from validator in _validators
                            where validator.Value.All(attribute => attribute.IsValid(_propertyGetters[validator.Key](this)))
                            select validator;

                var count = query.Count() - _validationExceptionCount;
                return count;
            }
        }

        /// <summary>
        /// Gets the number of properties which have a validation attribute
        /// </summary>
        public int TotalPropertiesWithValidationCount
        {
            get
            {
                return _validators.Count();
            }
        }

        public ValidationViewModel()
        {
            //Initialize dictionaries
            _propertyGetters = new Dictionary<string, Func<ValidationViewModel, object>>();
            _validators = new Dictionary<string, ValidationAttribute[]>();

            //Get all public properties belongs to this object
            var properties = GetType().GetProperties();


            properties.Where(p =>
                                 {
                                     //Get validation attributes list
                                     ValidationAttribute[] validationAttribute = GetValidations(p);
                                     if (validationAttribute.Length != 0)
                                     {
                                         _validators[p.Name] = validationAttribute;
                                         //Get Property Getter delegates and add into dictionary
                                         _propertyGetters[p.Name] = GetValueGetter(p);
                                         return true;
                                     }
                                     return false;
                                 }).ToArray();//To defeat defered execution ,I used ToArray() function
        }

        private ValidationAttribute[] GetValidations(PropertyInfo property)
        {
            return (ValidationAttribute[])property.GetCustomAttributes(typeof(ValidationAttribute), true);
        }

        private Func<ValidationViewModel, object> GetValueGetter(PropertyInfo property)
        {
            return viewmodel => property.GetValue(viewmodel, null);
            //The above statement will return the below lamda 
            // return new Func<ValidationViewModelBase, object>(viewmodel => property.GetValue(viewmodel, null));
        }

        private int _validationExceptionCount;

        public void ValidationExceptionsChanged(int count)
        {
            _validationExceptionCount = count;
            OnPropertyChanged();
        }
    }

}
